/**
 * 
 */
package t4j.concurrency;

import java.util.LinkedList;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.Random;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

//顾客
class Customer {
	private final int serviceTime;

	public Customer(int tm) {
		serviceTime = tm;
	}

	public int getServiceTime() {
		return serviceTime;
	}

	@Override
	public String toString() {

		return "[" + serviceTime + "]";
	}
}

// 顾客队列
class CustomerLine extends ArrayBlockingQueue<Customer> {

	public CustomerLine(int maxLineSize) {
		super(maxLineSize);
	}

	@Override
	public String toString() {

		if (this.size() == 0)
			return "[Empty]";

		StringBuilder result = new StringBuilder();

		//从队列中循环所有顾客到字符串
		for (Customer customer : this)
			result.append(customer);

		return result.toString();

	}
}

// 顾客生成器
class CustomerGenerator implements Runnable {
	private CustomerLine customers;
	private static Random rand = new Random(47);

	public CustomerGenerator(CustomerLine cq) {
		customers = cq;
	}

	public void run() {
		try {
			while (!Thread.interrupted()) {
				TimeUnit.MILLISECONDS.sleep(rand.nextInt(300));
				Customer c= new Customer(rand.nextInt(1000));
				//System.out.println("-"+c);
				customers.put(c);// 每隔n时间往 顾客队列 插入 顾客
				
			}
		} catch (InterruptedException e) {
			System.out.println("CustomerGenerator interrupted");
		}

		System.out.println("CustomerGenerator terminating");
	}
}

// 出纳员
class Teller implements Runnable, Comparable<Teller> {
	private static int counter = 0;
	private final int id = counter++;
	//客户担任这一转变过程中：
	private int customersServed = 0;
	private CustomerLine customers;
	private boolean servingCustomerLine = true;//是否正在服务客户队列

	public Teller(CustomerLine cq) {
		customers = cq;
	}

	// 比较出纳员服务过的顾客数，给优先级队列排序
	@Override
	public synchronized int compareTo(Teller o) {

		return customersServed < o.customersServed ? -1 : (customersServed == o.customersServed ? 0 : 1);
	}

	@Override
	public void run() {
		try {
			while (!Thread.interrupted()) {
				Customer customer = customers.take();// 从客户队列获取，消费顾客！如果没有顾客则阻塞
				TimeUnit.MILLISECONDS.sleep(customer.getServiceTime());//采用睡眠，模拟服务一个客户所需时间
				
				synchronized (this) {
					customersServed++;
					//System.out.println(this +":" + customersServed);
					while (!servingCustomerLine)//状态改变就阻塞
						wait();
				}
			}
		} catch (InterruptedException e) {
			System.out.println(this + "interrupted");
		}

		System.out.println(this + "terminating");
	}

	//给出纳员不同的工作或休息：
	public synchronized void doSomethingElse() {
		customersServed = 0;
		servingCustomerLine = false;
	}

	//把在休息的出纳员唤醒，服务客户队列
	public synchronized void serveCustomerLine() {
		assert !servingCustomerLine : "already serving: " + this;
		servingCustomerLine = true;
		notifyAll();//唤醒
	}

	@Override
	public String toString() {
		return "Teller " + id + " ";
	}

	public String shortString() {
		return "T" + id;
	}

}

// 出纳员管理者，每隔一段时间根据情况调整。这里管理正在工作、休息的出纳员队列
class TellerManager implements Runnable {
	private ExecutorService exec;
	private CustomerLine customers;
	private PriorityQueue<Teller> workingTellers = new PriorityQueue<Teller>();//正在工作的出纳员队列,根据优先级比较，将工作量最小的推向前台
	private Queue<Teller> tellersDoingOtherThings = new LinkedList<Teller>();//正在做其他其他事的出纳员队列
	private int adjustmentPeriod;//调整时间
	private static Random rand = new Random(47);

	public TellerManager(ExecutorService e, CustomerLine customers, int adjustmentPeriod) {
		exec = e;
		this.customers = customers;
		this.adjustmentPeriod = adjustmentPeriod;
		Teller teller = new Teller(customers);//传入顾客队列
		exec.execute(teller);//线程执行teller
		workingTellers.add(teller);//add出纳员
	}

	//调整出纳员数量
	public void adjustTellerNumber() {
		
		//这实际上是一个控制系统。通过调整的数字，你可以揭示稳定性问题控制机制。
	    //如果客户队列太长，添加另一个出纳员：
		if (customers.size() / workingTellers.size() > 2) {//队列总数 / 正在工作出纳员数 > 平均每人处理顾客数
			
			//如果出纳员有休息或做其他的工作，就带回来：
			if (tellersDoingOtherThings.size() > 0) {
				Teller teller = tellersDoingOtherThings.remove();//从队列中中获取并删除一个出纳员
				teller.serveCustomerLine();//唤醒阻塞的Teller
				workingTellers.offer(teller);//把出纳员放进
				return;
			}
			//否则就新建一个出纳员
			Teller teller = new Teller(customers);
			exec.execute(teller);
			workingTellers.add(teller);
			return;
		}
		//如果排队线够短，删除出纳员：
		if (workingTellers.size() > 1 && customers.size() / workingTellers.size() < 2)
			reassignOneTeller();//分配出纳员做其他事
		
		//如果没有客户队列没有了，我们循环删减出纳员，直到剩下一个
		if(customers.size() == 0)
			while(workingTellers.size() > 1)//循环，把所有出纳员分配做其他事，剩下一个
				reassignOneTeller();
	}
	//给出纳员不同的工作或休息：
	private void reassignOneTeller() {
		Teller teller = workingTellers.poll();//从正在工作中的出纳员队列里获取并删除一个
		teller.doSomethingElse();//修改出纳员状态为休息,当Teller线程扫到状态改变后，马上阻塞该Teller
		tellersDoingOtherThings.offer(teller);//把出纳员放进休息队列
	}

	@Override
	public void run() {
		try {
			while (!Thread.interrupted()) {

				TimeUnit.MILLISECONDS.sleep(adjustmentPeriod);

				adjustTellerNumber();//每隔一段时间调整一下出纳员的数量

				System.out.print(customers + " { ");//打印顾客

				for (Teller teller : workingTellers)
					System.out.print(teller.shortString() + " ");//循环打印正在工作的出纳员

				System.out.println("}");
			}
		} catch (InterruptedException e) {

			System.out.println(this + "interrupted");
		}

		System.out.println(this + "terminating");

	}

	@Override
	public String toString() {

		return "TellerManager ";
	}
}

public class BankTellerSimulation {

	static final int MAX_LINE_SIZE = 50;
	static final int ADJUSTMENT_PERIOD = 1000;

	public static void main(String[] args) throws Exception, InterruptedException {
		ExecutorService exec = Executors.newCachedThreadPool();

		CustomerLine customers = new CustomerLine(MAX_LINE_SIZE);//生成一个n 人的队列

		exec.execute(new CustomerGenerator(customers));//把队列传进顾客生成器里

		exec.execute(new TellerManager(exec, customers, ADJUSTMENT_PERIOD));

		args = new String[] { "10" };

		if (args.length > 0)
			TimeUnit.SECONDS.sleep(new Integer(args[0]));//5秒后终止线程
		else {
			System.out.println("Press 'Enter' to quit");
			System.in.read();
		}

		exec.shutdownNow();
	}
}
